(_=>{
    $byview.request = $byview.request ? $byview.request : {};
    const {util} = $byview;
    const RETURN_TYPE = {
        JSON:'json',
        PLAIN:'plain',
    };
    
    let _xhrs = {};
    let request = $byview.request;
    
    return Object.assign($byview.request, {
        _xhrs,
        RETURN_TYPE,
    
        unparseUrlQuery(params, raw) { return util.unparseUrlQuery.apply(null, arguments); },
        parseUrlQuery(query) { return util.parseUrlQuery.apply(null, arguments); },
        updateUrlQuery(url, queryParams, noMerge) {return util.updateUrlQuery.apply(null, arguments); },
    
        unparseUrlHash(hashInfo){ return util.unparseUrlHash(hashInfo); },
        parseUrlHash(url){ return util.parseUrlHash.apply(null, arguments); },
        updateUrlHashQuery(){ return util.updateUrlHashQuery.apply(null, arguments); },
        updateUrlHashSign(){ return util.updateUrlHashSign.apply(null, arguments); },
        updateUrlHashPath(){ return util.updateUrlHashPath.apply(null, arguments); },
        parseLocationHash() {
            return util.parseUrlHash(window.location.href);
        },
        updateLocationHashQuery(params, noMerge) {
            let newHash = util.updateUrlHashQuery(location.hash, params, noMerge);
        
            if(newHash.replace(/^#/, '') == location.hash.replace(/^#/, '')) {
                return;
            }
        
            window.$hashLock = window.$hashLock ? Number(window.$hashLock)+1 : 1;
            location.hash = newHash;
        },
        updateLocationHashSign(sign) {
            let newHash = util.updateUrlHashSign(location.hash, sign);
            if(newHash.replace(/^#/, '') == location.hash.replace(/^#/, '')) {
                return;
            }
        
            window.$hashLock = window.$hashLock ? Number(window.$hashLock)+1 : 1;
            location.hash = newHash;
        },
        updateLocationHashPath(path) {
            let newHash = util.updateUrlHashPath(location.hash, path);
            if(newHash.replace(/^#/, '') == location.hash.replace(/^#/, '')) {
                return;
            }
        
            window.$hashLock = window.$hashLock ? Number(window.$hashLock)+1 : 1;
            location.hash = newHash;
        },
    
        getUrlQuery(url) { return util.getUrlQuery.apply(null, arguments); },
        getUrlHashQuery(url) { return util.getUrlHashQuery.apply(null, arguments); },
    
        urlPath() { return util.urlPath.apply(null, arguments); },
    
        getXHR(id) {
            return _xhrs[id];
        },
    
        /**
         * @typedef IByviewRequest_SendOption
         * @member {string} method
         * @member {mixed} data
         * @member {bool} useDecode
         * @member {string} url
         * @member {string} returnType
         * @member {Object} header
         * @member {bool} async
         * @member {bool} appendByParams
         * @member {bool} noRandKey
         * @member {string} id
         * @member {Function} init
         * @member {Function} onProgress
         */
        /**
         * @typedef IByviewRequest_SendProgressEvent
         * @member {bool} lengthComputable
         * @member {int} loaded
         * @member {int} total
         * @member {int} speed
         * @member {int} percent
         * @member {String} percentString
         * @member {Event} originEvent
         */
        /**
         * 发送请求
         * @param {IByviewRequest_SendOption} option
         */
        async send(option) {
            option = option || {};
            option = Object.assign({
                method: 'get',
                data: {},
                useDecode: true,
                url: "",
                returnType: RETURN_TYPE.JSON,
                header: {},
                async: true,
                init: null,
                onProgress: null,
                id:null,
                randKey: false
            }, option);
        
            let method = option.method.toLowerCase();
            let returnType = option.returnType.toLowerCase();
            let url = option.url;
            let data = option.data || {};
            option.randKey = (option.randKey && typeof option.randKey == 'boolean') ? '__rk' : option.randKey;
        
            if(!['get', 'post', 'jsonp'].includes(method)) {
                console.error(`不支持的请求方法 ${method}`);
                return;
            }
        
            if(method == 'post') {
                if(option.randKey) {
                    let rkOption = {};
                    rkOption[option.randKey] = util.createUnqid();
                    url = util.updateUrlQuery(url, rkOption);
                }
            
                if(!(data instanceof FormData)) {
                    let forData = new FormData();
                    let unparseRaw = util.unparseUrlQuery(data, true);
                
                    for (let row of unparseRaw) {
                        forData.append(row.key, row.value);
                    }
                
                    data = forData;
                }
            } else if(!(data instanceof FormData)) {
                if(option.randKey) {
                    data[option.randKey] = util.createUnqid();
                }
                url = util.updateUrlQuery(url, data);
                data = null;
            }
        
            if(option.loading) {
                $byview.topframe.loading(typeof option.loading == 'string'  ? option.loading : '请稍后..');
            }
        
            if(method == 'jsonp') {
                let script = document.createElement('script');
                let callbackName = "byreq" + util.createUnqid();
                script.type = 'text/javascript';
                script.id = callbackName;
                url = util.updateUrlQuery(url, {callback: callbackName});
                script.src = url;
            
                return await new Promise((ok) => {
                    let executed = false;
                    let old = ok;
                    ok = ret => {
                        if(option.loading) {
                            $byview.topframe.closeLoading();
                        }
                    
                        old(ret);
                    };
                
                    window[callbackName] = function(ret) {
                        executed = true;
                        ok(ret);
                    }
                
                    script.onload = function() {
                        document.body.removeChild(script);
                        delete window[callbackName];
                    
                        if(!executed) {
                            console.error(callbackName + ' 没有回调被');
                            ok();
                        }
                    }
                
                    script.onerror = function(e) {
                        document.body.removeChild(script);
                        delete window[callbackName];
                        let err = "jsonp请求失败: '"+url+"'";
                        console.error(err, e);
                        ok();
                    }
                
                    document.body.appendChild(script);
                });
            } else {
                let xhr = new XMLHttpRequest();
                if(option.id) {
                    _xhrs[option.id] = xhr;
                }
            
                xhr.open(method, url, true);
                let prevTime = parseInt(new Date().getTime());
                let prevLoaded = 0;
            
                option.init && option.init(xhr);
                if(option.onProgress) {
                    xhr.upload.onprogress = function (e) {
                        let now = parseInt(new Date().getTime());
                        let cost = now - prevTime;
                        let speed = '0';
                        let percent, percentStr;
                    
                        if(e.lengthComputable) {
                            percent = parseFloat((e.loaded/e.total*100).toFixed(2));
                            percentStr = String(percent) + '%';
                        } else {
                            percent = 0;
                            percentStr = '--';
                        }
                    
                        if(cost != 0) {
                            speed = parseFloat((((e.loaded - prevLoaded) / cost) * 1000).toFixed(2));
                        }
                    
                        option.onProgress({
                            lengthComputable: e.lengthComputable,
                            loaded: e.loaded,
                            total: e.total,
                            percent,
                            percentStr,
                            speed,
                            originEvent: e
                        });
                    };
                
                    xhr.upload.onloadstart = function(e) {
                        xhr.upload.onprogress(e);
                    };
                }
            
                let promise = new Promise((ok) => {
                    let old = ok;
                    ok = ret => {
                        if(option.loading) {
                            $byview.topframe.closeLoading();
                        }
                    
                        old(ret);
                    };
                
                    xhr.onreadystatechange = function() {
                        if(xhr.readyState == XMLHttpRequest.DONE) {
                            let content = null;
                            if(option.id) {
                                delete _xhrs[option.id];
                            }
                        
                            if(xhr.status == 200) {
                                content = xhr.responseText;
                            } else {
                                let err = `请求'${url}'失败, 状态码: ${xhr.status}`;
                                console.error(err);
                                ok();
                                return;
                            }
                        
                            if(returnType == RETURN_TYPE.JSON) {
                                let result;
                            
                                if(option.rawContent) {
                                    ok(content);
                                    return;
                                }
                            
                                try {
                                    result = JSON.parse(content)
                                } catch (e) {
                                    result = 'JSON 解析失败';
                                    console.error(result, e)
                                    ok();
                                    return;
                                }
                            
                                ok(result);
                            } else if(returnType == RETURN_TYPE.PLAIN) {
                                ok(content);
                            } else {
                                console.error(`不支持的返回值类型: "${returnType}"`);
                                ok();
                            }
                        }
                    };
                
                    xhr.send(data);
                });
            
                promise.xhr = xhr;
                return await promise;
            }
        },
    
        /**
         * @param {String} url
         * @param {Object} params - 请求参数,键值对
         * @param {IByviewRequest_SendOption} option - 发送参数
         * @returns {Promise<unknown>}
         */
        async get(url, params, option) {
            option = option || {};
            delete option.url;
            delete option.method;
            delete option.params;
        
            return request.send(Object.assign({
                url,
                method:'get',
                data:params,
            }, option));
        },
    
        /**
         * @param {String} url
         * @param {Object} params - 请求参数,键值对
         * @param {IByviewRequest_SendOption} option - 发送参数
         * @returns {Promise<unknown>}
         */
        async jsonp(url, params, option) {
            option = option || {};
            delete option.url;
            delete option.method;
            delete option.params;
        
            return request.send(Object.assign({
                url,
                method:'jsonp',
                data:params,
            }, option));
        },
    
        /**
         * @param {String} url
         * @param {Object} data - 请求参数,键值对(不在url中)
         * @param {IByviewRequest_SendOption} option - 发送参数
         * @returns {Promise<unknown>}
         */
        async post(url, data, option) {
            option = option || {};
            delete option.url;
            delete option.method;
            delete option.params;
        
            return request.send(Object.assign({
                url,
                method:'post',
                data,
            }, option));
        },
    
        
    });
})();
